﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using StockFinder.Indicators.Day;
using StockFinder.Indicators.Day.ClosePrice;
using StockFinder.Indicators.Day.Volume;
using StockFinder.Model;
using log4net;
using StockFinder.StockLists;

namespace StockFinder.TradingSystems
{
    /*
     * The long term accumulator
     * ---------------------------------------------------
     * Buy on all time high break out with monthly volume 100%+
     * Hard stop loss at 2*ATR(14)
     * Trailing stop loss at 6*ATR(14)
     * No profit taking
     * Open positions are closed via stop loss - pesimistic
     */

    public class AlistairCookTradingSystem : ITradingSystem
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(AlistairCookTradingSystem));                   

        const string ATR14_NAME = "ATR14";
        const string MONTHLY_VOLUME = "VOL22";
        
        private readonly BaseDayIndicator _AtrIndicator = new ExponentialMovingAverageTrueRangeClosePriceDayIndicator(14, ATR14_NAME);
        private readonly BaseDayIndicator _MonthlyVolumeIndicator = new SimpleMovingAverageDailyVolumeDayIndicator(22, MONTHLY_VOLUME);

        public TradingSystemResult Run(TradingSystemParameters parameters, IStockList stockList)
        {
            var tradingSystemResult = new TradingSystemResult();            
            var stopWatch = new Stopwatch();
            var index = 0;

            var stockListCount = stockList.Count;

            stopWatch.Start();

            foreach (var symbol in stockList)
            {
                index++;                

                Log.DebugFormat("{0}", symbol.Name);

                if (symbol.Prices == null || symbol.Prices.Count == 0)
                {
                    Log.DebugFormat("Returned empty prices");
                    continue;
                }

                Log.DebugFormat("Loaded {0} prices", symbol.Prices.Count);

                var tradesForCurrentStock = Run(parameters, symbol);

                tradingSystemResult.Trades.AddRange(tradesForCurrentStock.Trades);

                Log.DebugFormat("{0}% completed", Math.Round(((index / (double)stockListCount) * 100), 2));
            }

            Log.DebugFormat("Total time taken: {0}:{1} (m:s)", 
                stopWatch.Elapsed.Minutes,
                stopWatch.Elapsed.Seconds);

            return tradingSystemResult;
        }

        public TradingSystemResult Run(TradingSystemParameters parameters, Symbol symbol)
        {
            Log.DebugFormat("Running AlistairCook system on {0}", symbol.Name);            

            #region Parameters check

            var systemParameters = parameters as AlistairCookTradingSystemParameters;
            if (systemParameters == null)
            {
                throw new ArgumentException("'parameters' must be of type AlistairCookTradingSystemParameters. Is either null or invalid cast.");
            }

            var trailingStopLossMultiplier = systemParameters.TrailingStopLossMultiplier;
            var hardStopLossMultiplier = systemParameters.HardStopMultiplier;
            var minimunAdjustedClosePrice = systemParameters.MinimunAdjustedClosePrice;
            var minumumMonthlyVolume = systemParameters.MinimunMonthlyAverageVolume;

            #endregion

            #region Apply technical indicators

            _AtrIndicator.ApplyIndicator(symbol.Prices);
            _MonthlyVolumeIndicator.ApplyIndicator(symbol.Prices);

            #endregion            

            #region Flags

            var openNewPosition = false;
            var adjustTrailingStopLoss = false;

            #endregion

            #region Position details

            var result = new TradingSystemResult();            
            Trade trade = null;

            #endregion

            #region Local variables
            var stopWatch = new Stopwatch();
            stopWatch.Start();
            var previousAllTimeHighClose = 0m;
            var priceCount = symbol.Prices.Count;
            //start from first price in history
            var pricesArray = symbol.Prices.OrderBy(p => p.PriceDate).ToArray();
            #endregion

            #region System loop

            //loop prices
            for (var i = 0; i < priceCount; i++)
            {
                #region Daily variables
                //var samplePrices = pricesArray.Take(i).ToList();                
                var todayPrice = pricesArray[i]; 
                #endregion

                #region Indicator history check

                //need enough indicator history
                if (i < _AtrIndicator.LookbackPeriod || 
                    i < _MonthlyVolumeIndicator.LookbackPeriod) 
                    continue;

                #endregion                

                #region Pre-market checks
              
                //do we need to close open position?
                if (trade != null)
                {
                    //use greater of trailing and hard
                    var stopLossToCheck = (trade.HardStopLoss > trade.TrailingStopLoss)
                                              ? trade.HardStopLoss
                                              : trade.TrailingStopLoss;

                    //did we break the stop loss?
                    var yesterdayPrice = pricesArray[i - 1];

                    if (yesterdayPrice.AdjustedClose < stopLossToCheck)
                    {
                        trade.ExitDate = todayPrice.PriceDate;
                        trade.ExitPrice = todayPrice.AdjustedOpen;
                        trade.IsOpen = false;
                        trade = null; //reset position
                    }                                        
                }

                //do we need to adjust trailing stop loss?
                if (adjustTrailingStopLoss)
                {
                    if (trade != null)
                    {
                        var yesterdayPrice = pricesArray[i - 1];

                        var currentTrailingStopLoss = yesterdayPrice.AdjustedClose -
                                                    (yesterdayPrice.DayIndicators[_AtrIndicator.IndicatorName] * trailingStopLossMultiplier);

                        //only adjust trailing stop loss if goes up, could be decrease in volatility
                        if (currentTrailingStopLoss > trade.TrailingStopLoss)
                        {
                            trade.TrailingStopLoss = currentTrailingStopLoss;
                        }
                    }

                    adjustTrailingStopLoss = false;
                }

                //do we need to open a position today?
                if (openNewPosition)
                {
                    var yesterdayPrice = pricesArray[i - 1];

                    //set position flags
                    openNewPosition = false;                    
                    trade = new Trade
                                   {
                                       Symbol = symbol.Name,
                                       IsOpen = true,
                                       EntryDate = todayPrice.PriceDate,
                                       EntryPrice = todayPrice.AdjustedOpen,
                                       HardStopLoss = todayPrice.AdjustedOpen -
                                                      (yesterdayPrice.DayIndicators[_AtrIndicator.IndicatorName]*hardStopLossMultiplier)
                                   };

                    //any technical details want to keep 
                    var breakoutDayMonthlyVolume = yesterdayPrice.DayIndicators[_MonthlyVolumeIndicator.IndicatorName];
                    var confirmationDayMonthlyVolume = todayPrice.DayIndicators[_MonthlyVolumeIndicator.IndicatorName];

                    trade.StateAtEntry["MonthAverageVolumeOnBreakoutDay"] =
                        yesterdayPrice.DayIndicators[_MonthlyVolumeIndicator.IndicatorName];

                    trade.StateAtEntry["AbsoluteVolumeOnBreakoutDay"] = yesterdayPrice.Volume;
                    trade.StateAtEntry["RelativeVolumeOnBreakoutDay"] = ((yesterdayPrice.Volume - breakoutDayMonthlyVolume) / breakoutDayMonthlyVolume) * 100;

                    trade.StateAtEntry["DaysOfPricesBeforeEntry"] = i;
                    trade.StateAtEntry["TotalDaysOfPrices"] = priceCount;                                        
                    
                    trade.StateAtEntry["PriceChangeOnConfirmationDay"] = ((todayPrice.AdjustedClose -
                                                                         yesterdayPrice.AdjustedClose)/
                                                                        yesterdayPrice.AdjustedClose)*100;
                    trade.StateAtEntry["AbsoluteVolumeOnConfirmationDay"] = todayPrice.Volume;
                    trade.StateAtEntry["RelativeVolumeOnConfirmationDay"] = ((todayPrice.Volume - confirmationDayMonthlyVolume) / confirmationDayMonthlyVolume) * 100;

                    result.Trades.Add(trade);
                }

                #endregion

                #region After market checks

                if (previousAllTimeHighClose == 0)
                {
                    var samplePrices = pricesArray.Take(i).ToList();

                    previousAllTimeHighClose = samplePrices.Max(p => p.AdjustedClose);
                }

                if (todayPrice.AdjustedClose > previousAllTimeHighClose)
                {
                    /* new all time high */                    

                    previousAllTimeHighClose = todayPrice.AdjustedClose;

                    //if we arent already in position set flag to open one on tomorrow's open
                    if (trade == null)
                    {                        
                        if (todayPrice.AdjustedClose > minimunAdjustedClosePrice &&
                            todayPrice.DayIndicators[_MonthlyVolumeIndicator.IndicatorName] >= minumumMonthlyVolume)
                        {
                            openNewPosition = true;   
                        }                        
                    }
                    else
                    {
                        adjustTrailingStopLoss = true;
                    }
                }               

                #endregion                
            }

            #endregion

            #region Open position check
            if (result.Trades.Count > 0)
            {
                trade = result.Trades.Last();
                var todayPrice = pricesArray.Last();

                //set exit as close price if open position
                if (trade.ExitPrice == 0)
                {
                    trade.ExitDate = todayPrice.PriceDate;

                    //pesimistic to use exit price but better best of underestimating
                    trade.ExitPrice = (trade.HardStopLoss > trade.TrailingStopLoss)
                                           ? trade.HardStopLoss
                                           : trade.TrailingStopLoss;
                }
            } 
            #endregion         

            Log.DebugFormat("{0} positions generated. Took {1}:{2} (s:ms)", 
                result.Trades.Count,
                stopWatch.Elapsed.Seconds,
                stopWatch.Elapsed.Milliseconds);

            Log.DebugFormat("Trade count: {0}", result.Trades.Count);
            Log.DebugFormat("Expectancy: {0}", Math.Round(result.GetExpectancy(), 2));
            Log.DebugFormat("Standard Deviation: {0}", Math.Round(result.GetStandardDeviation(), 2));
            Log.DebugFormat("SQN: {0}", Math.Round(result.GetSqn(), 2));

            return result;
        }
    }
}
